/*
 * @ProjectName: 编程学习
 * @Copyright:   2019 HangZhou Ashe Dev, Ltd. All Right Reserved.
 * @address:     https://yiyuery.github.io/NoteBooks/
 * @date:        2020/2/23 11:22 上午
 * @description: 本内容仅限于编程技术学习使用，转发请注明出处.
 */
package com.yi.yier.boot.autoconfigure.codegenerator.gen;

import com.google.common.base.CaseFormat;
import com.yi.yier.boot.autoconfigure.codegenerator.common.PropertyConstant;
import com.yi.yier.boot.autoconfigure.codegenerator.gen.extend.ITemplateContextBuilder;
import com.yi.yier.boot.autoconfigure.codegenerator.common.GenCodeParam;
import com.yi.yier.boot.autoconfigure.codegenerator.common.TemplateItem;
import com.yi.yier.boot.autoconfigure.codegenerator.constants.CodeGenConstants;
import com.yi.yier.boot.autoconfigure.codegenerator.common.PropertiesLoader;
import com.yi.yier.common.util.string.StringUtil;
import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.output.FileWriterWithEncoding;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.mybatis.generator.config.*;

import java.io.*;
import java.nio.file.Paths;
import java.util.*;

/**
 * <p>
 *
 * </p>
 *
 * @author Ashe
 * @version V1.0.0
 * @date 2020/2/23 11:22 上午
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人}  2020/2/23
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Slf4j
public abstract class AbstractCodeGenerator<T> implements ICodeGenerator {

    private static Configuration configuration = null;

    protected ITemplateContextBuilder templateContextBuilder;

    @Override
    public void registerContextBuilder(ITemplateContextBuilder contextBuilder) {
        this.templateContextBuilder = contextBuilder;
    }

    static {
        init();
    }

    /**
     * Mybatis 代码自动生成基本配置
     *
     * @return
     */
    public Context initMybatisGeneratorContext(String sign, String exportPath) {
        Context context = new Context(ModelType.FLAT);
        context.setId("Yier");//defaultContext
        //org.mybatis.generator.internal.ObjectFactory   MyBatis3Simple、MyBatis3
        context.setTargetRuntime("MyBatis3");

        //生成的Java文件的编码
        context.addProperty("javaFileEncoding", "utf-8");
        context.addProperty(PropertyRegistry.CONTEXT_BEGINNING_DELIMITER, "`");
        context.addProperty(PropertyRegistry.CONTEXT_ENDING_DELIMITER, "`");

        //使用columnOverride覆盖
        context.addProperty("autoDelimitKeywords", "false");

        //格式化java代码
        context.addProperty("javaFormatter", "org.mybatis.generator.api.dom.DefaultJavaFormatter");
        //格式化xml代码
        context.addProperty("xmlFormatter", "org.mybatis.generator.api.dom.DefaultXmlFormatter");

        //数据库连接
        JDBCConnectionConfiguration jdbcConnectionConfiguration = new JDBCConnectionConfiguration();
        jdbcConnectionConfiguration.setConnectionURL(PropertiesLoader.getProps().getProperty("db.url"));
        jdbcConnectionConfiguration.setUserId(PropertiesLoader.getProps().getProperty("db.username"));
        jdbcConnectionConfiguration.setPassword(PropertiesLoader.getProps().getProperty("db.password"));
        jdbcConnectionConfiguration.setDriverClass(PropertiesLoader.getProps().getProperty("db.driver"));
        context.setJdbcConnectionConfiguration(jdbcConnectionConfiguration);

        //生成sqlMapper XML文件
        SqlMapGeneratorConfiguration sqlMapGeneratorConfiguration = new SqlMapGeneratorConfiguration();
        sqlMapGeneratorConfiguration.setTargetProject(exportPath);
        sqlMapGeneratorConfiguration.setTargetPackage(PropertiesLoader.getProperty(PropertyConstant.YIER_GEN_PROP_PACKAGE_CORE_DAO_MAPPER).replace("{sign}", sign));
        sqlMapGeneratorConfiguration.addProperty("enableSubPackages", "true");
        context.setSqlMapGeneratorConfiguration(sqlMapGeneratorConfiguration);


        //Java模型生成equals和hashcode方法
        //addPluginConfiguration(context, "org.mybatis.generator.plugins.EqualsHashCodePlugin", new Properties());
        //为生成的Java模型创建一个toString方法
        //addPluginConfiguration(context, "org.mybatis.generator.plugins.ToStringPlugin", new Properties());
        //生成在XML中的<cache>元素
        addPluginConfiguration(context, "org.mybatis.generator.plugins.CachePlugin", new Properties());
        //生成Model中的Lombok注解 & 自定义开关
        //生成Model中的Swagger的ApiModel注解 & 自定义开关
        addPluginConfiguration(context, "com.yi.yier.boot.autoconfigure.codegenerator.plugins.XPlugin",
                new LinkProperties().setProperties("suppressLombok", PropertiesLoader.getProperty(PropertyConstant.YIER_GEN_PROP_PLUGIN_LOMBOK))
                        .setProperties("suppressSwagger", PropertiesLoader.getProperty(PropertyConstant.YIER_GEN_PROP_PLUGIN_SWAGGER))
                        .over());

        //设置是否生成注释
        CommentGeneratorConfiguration commentGeneratorConfiguration = new CommentGeneratorConfiguration();
        //是否关闭所有注释
        commentGeneratorConfiguration.addProperty("suppressAllComments", "false");
        //是否生成注释代时间戳
        commentGeneratorConfiguration.addProperty("suppressDate", "false");
        //生成字段注释
        commentGeneratorConfiguration.addProperty("addRemarkComments", "true");

        commentGeneratorConfiguration.setConfigurationType("com.yi.yier.boot.autoconfigure.codegenerator.internal.XCommentGenerator");

        context.setCommentGeneratorConfiguration(commentGeneratorConfiguration);

        return context;
    }

    /**
     * 添加插件
     *
     * @param context
     * @param configurationType
     * @param properties
     */
    private void addPluginConfiguration(Context context, String configurationType, Properties properties) {
        PluginConfiguration pluginConfiguration = new PluginConfiguration();
        pluginConfiguration.setConfigurationType(configurationType);
        if (Objects.nonNull(properties) && !properties.isEmpty()) {
            Enumeration<?> enu = properties.propertyNames();
            while (enu.hasMoreElements()) {
                Object key = enu.nextElement();
                pluginConfiguration.addProperty(String.valueOf(key), String.valueOf(properties.get(key)));
            }
        }
        context.addPluginConfiguration(pluginConfiguration);

    }

    /**
     * 生成简单名称代码
     * eg:
     * genCode("gen_test_demo");  gen_test_demo ==> Demo
     *
     * @param tableNames 表名, 可以多表
     */
    public List<GenCodeParam> genCodeWithSimpleName(String... tableNames) {
        return genCodeByTableName(true, tableNames);
    }

    /**
     * 生成具体名称代码
     * eg:
     * genCode("gen_test_demo");  gen_test_demo ==> GenTestDemo
     *
     * @param tableNames 表名, 可以多表
     */
    public List<GenCodeParam> genCodeWithDetailName(String... tableNames) {
        return genCodeByTableName(false, tableNames);
    }

    /**
     * 生成自定义名称代码
     * eg:
     * genCode("gen_test_demo", "IDemo");  gen_test_demo ==> IDemo
     *
     * @param tableName 表名, 只能单表
     */
    public GenCodeParam genCodeWithCustomName(String tableName, String customModelName) {
        return genCodeByTableName(tableName, customModelName, false);
    }

    /**
     * 下划线转成驼峰, 首字符为小写
     * eg: gen_test_demo ==> genTestDemo
     *
     * @param tableName 表名, eg: gen_test_demo
     * @return
     */
    protected String tableNameConvertLowerCamel(String tableName) {
        return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, tableName.toLowerCase());
    }

    /**
     * 下划线转成驼峰, 首字符为大写
     * eg: gen_test_demo ==> GenTestDemo
     *
     * @param tableName 表名, eg: gen_test_demo
     * @return
     */
    protected String tableNameConvertUpperCamel(String tableName) {
        return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, tableName.toLowerCase());
    }

    /**
     * 表名转成映射路径
     * eg: gen_test_demo ==> /gen/test/demo
     *
     * @param tableName 表名
     * @return
     */
    protected String tableNameConvertMappingPath(String tableName) {
        tableName = tableName.toLowerCase();
        return File.separator + (tableName.contains("_") ? tableName.replaceAll("_", File.separator) : tableName);
    }

    /**
     * ModelName转成映射路径
     * eg: Demo ==> /demo
     *
     * @param modelName
     * @return
     */
    protected String modelNameConvertMappingPath(String modelName) {
        String tableName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, modelName);
        return tableNameConvertMappingPath(tableName);
    }

    /**
     * 获取表的区分字段
     *
     * @param tableName 表名, eg: gen_test_demo
     * @return 区分字段 eg: test
     */
    protected String getSign(String tableName) {
        return getTableNameSplit(tableName)[1];
    }

    /**
     * 获取默认 modelName
     *
     * @param tableName 表名
     * @return
     */
    protected String getDefModelName(String tableName) {
        String[] strs = getTableNameSplit(tableName);
        StringBuilder sb = new StringBuilder();
        for (int i = 2; i < strs.length; i++) {
            sb.append(StringUtil.toUpperCaseFirstOne(strs[i].toLowerCase()));
        }
        return sb.toString();
    }

    /**
     * 获取表名切割后的数组
     *
     * @param tableName 表名
     * @return
     */
    private String[] getTableNameSplit(String tableName) {
        String[] strs = tableName.toLowerCase().split("_");
        if (!tableName.contains("_") || strs.length < 3) {
            throw new RuntimeException("表名格式不正确, 请按规定格式! 例如: gen_test_demo");
        }
        return strs;
    }

    /**
     * 通过数据库表名, 生成代码
     * 如表名为 gen_test_demo
     * 将生成  Demo & DemoMapper & DemoService & DemoServiceImpl & DemoController
     *
     * @param flag       标志
     * @param tableNames 表名数组
     */
    private List<GenCodeParam> genCodeByTableName(boolean flag, String... tableNames) {
        List<GenCodeParam> params = new ArrayList<>();
        for (String tableName : tableNames) {
            params.add(genCodeByTableName(tableName, null, flag));
        }
        return params;
    }

    /**
     * 通过数据库表名, 和自定义 modelName 生成代码
     * 如表名为 gen_test_demo, 自定义 modelName 为 IDemo
     * 将生成  IDemo & IDemoMapper & IDemoService & IDemoServiceImpl & IDemoController
     *
     * @param tableName 表名
     * @param modelName 实体类名
     * @param flag      标志
     */
    private GenCodeParam genCodeByTableName(String tableName, String modelName, boolean flag) {
        String sign = getSign(tableName);
        if (flag) {
            modelName = getDefModelName(tableName);
        }
        return GenCodeParam.builder()
                .tableName(tableName)
                .modelName(modelName)
                .sign(sign)
                .build();
    }

    /**
     * Freemarker 模板环境配置
     *
     * @return
     * @throws IOException
     */
    private static Configuration initFreemarkerConfiguration() {
        Configuration cfg = null;
        try {
            cfg = new Configuration(Configuration.VERSION_2_3_23);
            cfg.setDirectoryForTemplateLoading(new File(PropertiesLoader.getProperty(PropertyConstant.YIER_GEN_PROP_TEMPLATE_LOCATION)));
            cfg.setDefaultEncoding("UTF-8");
            cfg.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
        } catch (IOException e) {
            throw new RuntimeException("Freemarker 模板环境初始化异常!", e);
        }
        return cfg;
    }

    /**
     * 增加 Mapper 插件
     * initMybatisGeneratorContext 中结尾调用，会影响默认BaseMapper的接口xml实现
     *
     * @param context
     */
    private void addMapperPlugin(Context context) {
        PluginConfiguration pluginConfiguration = new PluginConfiguration();
        pluginConfiguration.setConfigurationType("tk.mybatis.mapper.generator.MapperPlugin");
        pluginConfiguration.addProperty("mappers", PropertiesLoader.getProps().getProperty("mapper.interface.reference"));
        context.addPluginConfiguration(pluginConfiguration);
    }

    /**
     * 初始化配置信息
     */
    private static void init() {
        PropertiesLoader.init();
        initTemplateConfiguration();
    }

    private static void initTemplateConfiguration() {
                switch (CodeGenConstants.TemplateKey.vOf(PropertiesLoader.getProperty(PropertyConstant.YIER_GEN_PROP_TEMPLATE_TYPE))) {
                    case VELOCITY:
                        //初始化Velocity脚本加载位置
                        Properties vprops = new Properties();
                        PropertiesLoader.getProps().setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, PropertiesLoader.getProps().getProperty(PropertyConstant.YIER_GEN_PROP_TEMPLATE_LOCATION));
                        Velocity.init(vprops);
                        break;
                    case FREEMARKER:
                        //初始化FreeMarker脚本加载位置
                        if (configuration == null) {
                            configuration = initFreemarkerConfiguration();
                }
                break;
            default:
                break;
        }
    }


    @SafeVarargs
    protected final <T> void genCodeWithFreemarkerTemplate(TemplateItem<T>... items) {
        if (Objects.nonNull(items) && items.length > 0) {
            Arrays.asList(items).forEach(p -> {
                try {
                    configuration.getTemplate(p.getTemplateName()).process(p.getContextMap(), new FileWriter(p.getTargetPath()));
                    log.info("{}生成成功！", p.getClassName());
                } catch (Exception e) {
                    log.error("{}生成失败！", p.getClassName());
                }
            });
        }
    }


    @SafeVarargs
    protected final <T> void genCodeWithVelocityTemplate(TemplateItem<T>... items) {
        if (Objects.nonNull(items) && items.length > 0) {
            Arrays.asList(items).forEach(p -> {
                try {
                    FileWriterWithEncoding fileWriter = new FileWriterWithEncoding(p.getTargetPath(), "utf-8");
                    Velocity.getTemplate(p.getTemplateName(), "utf-8").merge((VelocityContext) p.getContextMap(), fileWriter);
                    fileWriter.close();
                    log.info("{}生成成功！", p.getClassName());
                } catch (Exception e) {
                    log.error("{}生成失败！", p.getClassName());
                }
            });
        }
    }

    /**
     * 预置页面所需数据
     *
     * @param genCodeParam        生成参数
     * @param domainModelNameUpper 首字为大写的实体类名
     * @return
     */
    protected final <T> T getTemplateContext(GenCodeParam genCodeParam, String domainModelNameUpper, Class<T> clazz) {
        return templateContextBuilder.getTemplateContext(genCodeParam, domainModelNameUpper, clazz);
    }

    /**
     * 初始化父级目录和待写入文件
     *
     * @param path
     * @return
     */
    protected File initExportFile(String path) {
        File serviceFile = new File(path);
        // 查看父级目录是否存在, 不存在则创建
        if (!serviceFile.getParentFile().exists()) {
            serviceFile.getParentFile().mkdirs();
        }
        return serviceFile;
    }

    @Getter
    private class LinkProperties {
        Properties properties;

        LinkProperties() {
            properties = new Properties();
        }

        LinkProperties setProperties(String key, String value) {
            properties.setProperty(key, value);
            return this;
        }

        Properties over() {
            return getProperties();
        }
    }
}
